module jinji.util.tag;

import jinji.common.type;
import jinji.config;
import jinji.util.error;
import jinji.util.string;
import std.array : appender;
import std.string : cmp;
import tame.builtins;
public import tame.conv : text;

/// 验证标签名
extern(C) TagName parseTag(string name, bool allowEmpty = false) {
	import std.ascii;

	if (allowEmpty && name.length == 0)
		return cast(TagName)name;

	checkArg(name.stringof, name.length != 0, "标签名不能为空");
	checkArgRange(name.stringof, name.length <= maxTagLength, "标签名过长");
	checkArg(name.stringof, name[0] != '-' && name[0] != '"', `标签名不能以'-'或'"'开头`);
	checkArg(name.stringof, !isWhite(name[0]), "标签名不能以空白字符开头");
	foreach (c; name) {
		checkArg(name.stringof, c >= ' ', "标签名包含非法字符");
	}
	checkArg(name.stringof, !isWhite(name[$ - 1]), "标签名不能以空白字符结尾");
	return cast(TagName)name;
}

/// 验证标签串
Tags parseTags(string tags, Cstr function(string tag) callback = null) {
	import std.string : cmp;

	Cstr lastTag;
	foreach (tag; Splitter(tags)) {
		const c = cmp(cast(Cstr)parseTag(tag), lastTag);
		checkArg(tags.stringof, c > 0, text("'", tag, "'重复或未按顺序排列"));
		lastTag = callback ? callback(tag) : tag;
	}
	return cast(Tags)tags;
}

@safe:

/++
标签分割器

分割规则：
1. 以空格分割
2. 以双引号包裹的字符串作为一个整体
3. 双引号内的空格不分割
+/
struct Splitter {
	string s;
	size_t frontLen;

	@property bool empty() const => s.length == 0;

	@property auto front() @trusted {
		import core.stdc.string : memchr;

		if (!frontLen && s.length) {
			int c = void;
			if (s[0] == '"') {
				s = s[1 .. $];
				c = '"';
			} else {
				c = ' ';
			}
			const p = memchr(s.ptr, c, s.length);
			frontLen = p ? p - cast(void*)s.ptr : s.length;
		}
		return s[0 .. frontLen];
	}

	void popFront() {
		s = s[frontLen + (frontLen < s.length) .. $];
		frontLen = 0;
	}
}

///
unittest {
	auto s = Splitter("foo");
	assert(!s.empty);
	assert(s.front == "foo");
	s.popFront;
	assert(s.empty);

	s = Splitter(`""`);
	assert(!s.empty);
	assert(s.front == "");
}

///
unittest {
	auto s = Splitter("foo bar baz");
	assert(s.front == "foo");
	s.popFront;
	assert(s.front == "bar");
	s.popFront;
	assert(s.front == "baz");
	s.popFront;
	assert(s.empty);

	s = Splitter(`"foo bar"baz`);
	assert(s.front == "foo bar");
	s.popFront;
	assert(s.front == "baz");
	s.popFront;
	assert(s.empty);

	s = Splitter(`a"b" c`);
	assert(s.front == `a"b"`);
	s.popFront;
	assert(s.front == "c");
	s.popFront;
	assert(s.empty);
}

/// 检查结果
enum Result {
	/// 有效
	valid,
	/// 无效的字符
	invalidChar,
	/// 重复的标签
	dupTags,
	/// 标签顺序错误
	disordered
}

/++
验证标签字符串是否有效
Params:
	tags = 标签串
Returns: 检查结果
+/
Result verify(string tags) {
	auto s = Splitter(tags);
	string lastTag;
	for (; !s.empty; s.popFront()) {
		auto tag = s.front;
		if (!tag.length || tag[0] == '-')
			return Result.invalidChar;
		foreach (char c; tag) {
			if (unlikely(c == '\\' || c == '*'))
				return Result.invalidChar;
		}
		const c = cmp(tag, lastTag);
		if (unlikely(c == 0))
			return Result.dupTags;
		if (c < 0)
			return Result.disordered;
		lastTag = tag;
	}
	return Result.valid;
}

/++
获取a到b的增量
Params:
	a = 标签串
	b = 标签串
Returns: 增量
+/
string getPatch(string a, string b) {
	auto r = appender!string;
	auto sb = Splitter(b);
	{
		auto sa = Splitter(a);
		while (!sa.empty && !sb.empty) {
			auto x = sa.front;
			auto y = sb.front;
			auto c = cmp(x, y);
			if (c < 0) {
				if (r[].length) {
					r ~= ' ';
				}
				r ~= '-';
				r ~= x;
				sa.popFront();
			} else if (c > 0) {
				if (r[].length) {
					r ~= ' ';
				}
				r ~= y;
				sb.popFront();
			} else {
				sa.popFront();
				sb.popFront();
			}
		}
		for (; !sa.empty; sa.popFront()) {
			if (r[].length) {
				r ~= ' ';
			}
			r ~= '-';
			r ~= sa.front;
		}
	}
	for (; !sb.empty; sb.popFront()) {
		b = sb.front;
		assert(b.length, "Illegal patch string");
		if (r[].length) {
			r ~= ' ';
		}
		r ~= b;
	}
	return r[];
}

///
unittest {
	assert(getPatch("", "") == "");
	assert(getPatch("", "a") == "a");
	assert(getPatch("b", "") == "-b");
	assert(getPatch("a", "b") == "-a b");
	assert(getPatch("a b c", "b c") == "-a");
	assert(getPatch("a c", "b") == "-a b -c");
}

/++
将增量应用到标签字符串
Params:
	s = 标签串
	patch = 增量
Returns: 应用后的标签字符串
+/
string applyPatch(string s, string patch) {
	auto sb = Splitter(patch);
	if (sb.empty)
		return s;

	auto r = appender!string;
	{
		auto sa = Splitter(s);
		while (!sa.empty && !sb.empty) {
			auto x = sa.front;
			auto b = sb.front;
			assert(x.length, "Illegal tag string");
			assert(b.length, "Illegal patch string");
			auto y = b[0] == '-' ? b[1 .. $] : b;
			assert(y.length, "Illegal patch string");
			auto c = cmp(x, y);
			if (c <= 0) {
				if (c || b[0] != '-') {
					if (r[].length) {
						r ~= ' ';
					}
					r ~= x;
				}
				sa.popFront();
				if (c == 0)
					sb.popFront();
			} else {
				if (b[0] != '-') {
					if (r[].length) {
						r ~= ' ';
					}
					r ~= y;
				}
				sb.popFront();
			}
		}
		for (; !sa.empty; sa.popFront()) {
			if (r[].length) {
				r ~= ' ';
			}
			r ~= sa.front;
		}
	}
	for (; !sb.empty; sb.popFront()) {
		auto b = sb.front;
		assert(b.length, "Illegal patch string");
		if (b[0] != '-') {
			if (r[].length) {
				r ~= ' ';
			}
			r ~= b;
		} else
			assert(b.length > 1, "Illegal patch string");
	}
	return r[];
}

///
unittest {
	assert(applyPatch("", "") == "");
	assert(applyPatch("", "a") == "a");
	assert(applyPatch("b", "") == "b");
	assert(applyPatch("a", "b") == "a b");
	assert(applyPatch("a", "a") == "a");
	assert(applyPatch("a b", "b") == "a b");
	assert(applyPatch("a c", "b") == "a b c");
	assert(applyPatch("a", "-a") == "");
	assert(applyPatch("a", "b -c") == "a b");
	assert(applyPatch("a b", "-a") == "b");
	assert(applyPatch("a b", "-b") == "a");
}

/++
合并两个增量
Params:
	a = 增量a
	b = 增量b
Returns: 合并后的增量
+/
string mergePatch(string a, string b) {
	auto sb = Splitter(b);
	if (sb.empty)
		return a;

	auto r = appender!string;
	{
		auto sa = Splitter(a);
		while (!sa.empty && !sb.empty) {
			a = sa.front;
			assert(a.length, "Illegal tag string");
			auto x = a[0] == '-' ? a[1 .. $] : a;
			assert(x.length, "Illegal tag string");
			b = sb.front;
			assert(b.length, "Illegal patch string");
			auto y = b[0] == '-' ? b[1 .. $] : b;
			assert(y.length, "Illegal patch string");
			auto c = cmp(x, y);
			if (c <= 0) {
				if (c || a[0] == '-' || b[0] != '-') {
					if (r[].length) {
						r ~= ' ';
					}
					r ~= c == 0 && a[0] == '-' && b[0] != '-' ? x : a;
				}
				sa.popFront();
				if (c == 0)
					sb.popFront();
			} else {
				if (r[].length) {
					r ~= ' ';
				}
				r ~= b;
				sb.popFront();
			}
		}
		for (; !sa.empty; sa.popFront()) {
			if (r[].length) {
				r ~= ' ';
			}
			r ~= sa.front;
		}
	}
	for (; !sb.empty; sb.popFront()) {
		b = sb.front;
		assert(b.length && (b[0] != '-' || b.length > 1), "Illegal patch string");
		if (r[].length) {
			r ~= ' ';
		}
		r ~= b;
	}
	return r[];
}

///
unittest {
	assert(mergePatch("", "") == "");
	assert(mergePatch("", "a") == "a");
	assert(mergePatch("", "-a") == "-a");
	assert(mergePatch("b", "") == "b");
	assert(mergePatch("a", "b") == "a b");
	assert(mergePatch("a", "a") == "a");
	assert(mergePatch("a b", "b") == "a b");
	assert(mergePatch("a c", "b") == "a b c");
	assert(mergePatch("a", "-a") == "");
	assert(mergePatch("b", "-a") == "-a b");
	assert(mergePatch("-a", "a") == "a");
	assert(mergePatch("-a", "-a") == "-a");
	assert(mergePatch("a", "b -c") == "a b -c");
	assert(mergePatch("a -c", "b") == "a b -c");
	assert(mergePatch("b -c", "-b") == "-c");
	assert(mergePatch("-b -c", "c") == "-b c");
}
